summaryrefslogtreecommitdiff
path: root/apps/web/app/shared/[token]/page.tsx
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-08 07:07:59 -0800
committerFuwn <[email protected]>2026-02-08 07:07:59 -0800
commita33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1 (patch)
tree7d44bdcb94cc1b69fbc201a4757f27f3751c5adb /apps/web/app/shared/[token]/page.tsx
parentchore: gate Vercel analytics and speed insights to production only (diff)
downloadasa.news-a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1.tar.xz
asa.news-a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1.zip
feat: share with highlighted excerpt and fix auth redirect URLs
Add "share" button to text selection toolbar so users can share an entry with a highlighted passage visible to visitors. The public share page renders the highlight and scrolls to it on load. Also fix magic link and password reset redirects to use NEXT_PUBLIC_APP_URL instead of window.location.origin so emails link to the production domain.
Diffstat (limited to 'apps/web/app/shared/[token]/page.tsx')
-rw-r--r--apps/web/app/shared/[token]/page.tsx51
1 files changed, 35 insertions, 16 deletions
diff --git a/apps/web/app/shared/[token]/page.tsx b/apps/web/app/shared/[token]/page.tsx
index 222c1c8..7c7a463 100644
--- a/apps/web/app/shared/[token]/page.tsx
+++ b/apps/web/app/shared/[token]/page.tsx
@@ -1,14 +1,28 @@
import type { Metadata } from "next"
import { createSupabaseAdminClient } from "@/lib/supabase/admin"
import { sanitizeEntryContent } from "@/lib/sanitize"
+import { SharedEntryContent } from "./shared-entry-content"
interface SharedPageProperties {
params: Promise<{ token: string }>
}
+interface SharedHighlightData {
+ highlightedText: string
+ textOffset: number
+ textLength: number
+ textPrefix: string
+ textSuffix: string
+}
+
interface SharedEntryRow {
entry_id: string
expires_at: string | null
+ highlighted_text: string | null
+ highlight_text_offset: number | null
+ highlight_text_length: number | null
+ highlight_text_prefix: string | null
+ highlight_text_suffix: string | null
entries: {
id: string
title: string | null
@@ -30,7 +44,7 @@ async function fetchSharedEntry(token: string) {
const { data, error } = await adminClient
.from("shared_entries")
.select(
- "entry_id, expires_at, entries!inner(id, title, url, author, summary, content_html, published_at, enclosure_url, feeds!inner(title))"
+ "entry_id, expires_at, highlighted_text, highlight_text_offset, highlight_text_length, highlight_text_prefix, highlight_text_suffix, entries!inner(id, title, url, author, summary, content_html, published_at, enclosure_url, feeds!inner(title))"
)
.eq("share_token", token)
.maybeSingle()
@@ -43,7 +57,22 @@ async function fetchSharedEntry(token: string) {
return { expired: true as const }
}
- return { expired: false as const, entry: row.entries }
+ let highlightData: SharedHighlightData | null = null
+ if (
+ row.highlighted_text &&
+ row.highlight_text_offset !== null &&
+ row.highlight_text_length !== null
+ ) {
+ highlightData = {
+ highlightedText: row.highlighted_text,
+ textOffset: row.highlight_text_offset,
+ textLength: row.highlight_text_length,
+ textPrefix: row.highlight_text_prefix ?? "",
+ textSuffix: row.highlight_text_suffix ?? "",
+ }
+ }
+
+ return { expired: false as const, entry: row.entries, highlightData }
}
export async function generateMetadata({
@@ -67,18 +96,6 @@ export async function generateMetadata({
}
}
-function SanitisedContent({ htmlContent }: { htmlContent: string }) {
- // Content is sanitised via sanitize-html before rendering
- const sanitisedHtml = sanitizeEntryContent(htmlContent)
- return (
- <div
- className="prose-reader text-text-secondary"
- // eslint-disable-next-line react/no-danger -- content sanitised by sanitize-html
- dangerouslySetInnerHTML={{ __html: sanitisedHtml }}
- />
- )
-}
-
export default async function SharedPage({ params }: SharedPageProperties) {
const { token } = await params
const result = await fetchSharedEntry(token)
@@ -106,6 +123,7 @@ export default async function SharedPage({ params }: SharedPageProperties) {
}
const entry = result.entry
+ const sanitisedHtml = sanitizeEntryContent(entry.content_html || entry.summary || "")
const formattedDate = entry.published_at
? new Date(entry.published_at).toLocaleDateString("en-GB", {
day: "numeric",
@@ -133,8 +151,9 @@ export default async function SharedPage({ params }: SharedPageProperties) {
/>
</div>
)}
- <SanitisedContent
- htmlContent={entry.content_html || entry.summary || ""}
+ <SharedEntryContent
+ sanitisedHtml={sanitisedHtml}
+ highlightData={result.highlightData}
/>
</article>
<footer className="mt-12 border-t border-border pt-4 text-text-dim">